﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ObjectCache
{
    public class CacheManager : IDisposable
    {

        public void ResetCaches(long resetFilter)
        {
            for (int i = 0; i < mCacheJobs.Count; i++)
            {
                mCacheJobs[i].Reset(resetFilter);
            }
        }

        public void RemoveAllCaches()
        {
            mCacheJobs.Clear();
        }

        private List<ICacheJob> mCacheJobs = new List<ICacheJob>();

        private static CacheManager mCacheManager;
        private static readonly object mLockObj = new object();
        static CacheManager()
        {
            lock (mLockObj)
            {
                if (mCacheManager == null)
                    mCacheManager = new CacheManager();
            }
        }

        public static CacheManager Instance
        {
            get
            {
                return mCacheManager;
            }
        }

        private Thread mJobThread = null;
        private List<ICacheJob> mPendingRemovedList = new List<ICacheJob>();
        private WeakReference mIniThread = null;
        private CacheManager()
        {
            mJobThread = new Thread(() =>
            {
                while (true)
                {
                    lock (mLockObj)
                    {
                        mPendingRemovedList.ForEach(n =>
                        {
                            mCacheJobs.Remove(n);
                            n.Dispose();
                        });
                        mPendingRemovedList.Clear();
                    }

                    for (int i = 0; i < mCacheJobs.Count; i++)
                    {
                        var cacheJob = mCacheJobs[i];
                        try
                        {
                            cacheJob.CheckExpiration();
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine(cacheJob.GetType().ToString() + " synchronous issue:" + ex.Message);
                        }
                    }


                    Thread.Sleep(2000);
                }
            });
            mJobThread.Priority = ThreadPriority.Lowest;
            mJobThread.Start();
        }

        #region IDisposable Members

        public void Dispose()
        {
            mJobThread.Abort();
            RemoveAllCaches();

            if (mIniThread != null)
            {
                var t = mIniThread.Target as Thread;
                if (t != null)
                    t.Abort();
            }
        }

        #endregion

        public void Init(Action iniAction)
        {

            if (mIniThread != null)
            {
                var t = mIniThread.Target as Thread;
                if (t != null)
                {
                    if (t.IsAlive)
                        t.Abort();
                }
            }

            mIniThread = new WeakReference(new Thread(
                () => iniAction()
                ));

            ((Thread)mIniThread.Target).Priority = ThreadPriority.Normal;
            ((Thread)mIniThread.Target).Start();
        }

        public bool IsIniJobStart { get; set; }

        public void WaitWhileInitStart()
        {
            var t = mIniThread.Target as Thread;
            if (t != null)
            {
                while (true)
                {
                    if (t.ThreadState == System.Threading.ThreadState.Running && IsIniJobStart)
                    {
                        IsIniJobStart = false;
                        break;
                    }
                    Thread.Sleep(5);
                }
            }
        }

        private ICache<TKey, TValue> GetSourceCache<TKey, TValue>() where TKey : IEquatable<TKey>
        {

            var obj = mCacheJobs.Find(n => (n is Cache<TKey, TValue>));
            if (obj == null)
            {
                lock (mLockObj)
                {
                    obj = mCacheJobs.Find(n => (n is Cache<TKey, TValue>));
                    if (obj == null)
                    {
                        obj = new Cache<TKey, TValue>();
                        mCacheJobs.Add(obj);
                    }
                }
            }
            return (Cache<TKey, TValue>)obj;
        }

        public ICache<TKey, TValue> GetCache<TKey, TValue>() where TKey : IEquatable<TKey>
        {
            return new CacheWrapper<TKey, TValue>(GetSourceCache<TKey, TValue>());
        }

        public ICache<TKey, TValue> GetCache<TKey, TValue>(long resetFilter) where TKey : IEquatable<TKey>
        {
            return new CacheWrapper<TKey, TValue>(GetSourceCache<TKey, TValue>(), resetFilter);
        }

        public void RemoveCache<TKey, TValue>() where TKey : IEquatable<TKey>
        {
            var obj = mCacheJobs.Find(n => (n is Cache<TKey, TValue>));
            if (obj != null)
            {
                mPendingRemovedList.Add(obj);
            }

        }
    }

}
